import socket
import os
import hashlib
import json


class FileSyncClient:
    def __init__(self, host='localhost', port=8888):
        self.host = host
        self.port = port
        self.client_socket = None

    def connect_to_server(self):
        """连接到服务器"""
        try:
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.client_socket.connect((self.host, self.port))
            print(f"已连接到服务器 {self.host}:{self.port}")
            return True
        except Exception as e:
            print(f"连接服务器失败: {e}")
            return False

    def disconnect_from_server(self):
        """断开与服务器的连接"""
        if self.client_socket:
            self.client_socket.close()
            self.client_socket = None
            print("已断开与服务器的连接")

    def sync_directory(self, local_dir_path):
        """同步目录结构和文件到本地"""
        try:
            # 发送SYNC命令给服务器
            self.client_socket.send(b"SYNC")
            json_max_length = 8192
            # 接收服务器返回的文件树信息
            file_tree_json = self.client_socket.recv(json_max_length).decode('utf-8')  # bug: json长度可能大于json_mat_length

            # 添加调试输出以查看实际接收到的数据
            print(f"接收到的JSON数据长度: {len(file_tree_json)}")
            print(f"接收到的JSON数据前100字符: {file_tree_json[:100]}")

            file_tree = json.loads(file_tree_json)

            print("开始同步目录结构...")
            self.sync_directory_structure(local_dir_path, file_tree)

            # 发送确认消息给服务器
            self.client_socket.send(b"READY")

            # 接收并同步文件内容
            self.receive_files(local_dir_path, file_tree)

        except json.JSONDecodeError as e:
            print(f"JSON解析错误: {e}")
            print(f"接收到的原始数据: {file_tree_json}")
        except Exception as e:
            print(f"目录同步失败: {e}")

    def sync_directory_structure(self, local_dir_path, server_file_tree):
        """同步目录结构"""
        try:
            # 创建本地目录结构
            if not os.path.exists(local_dir_path):
                os.makedirs(local_dir_path)

            self.create_local_structure(local_dir_path, server_file_tree)

        except Exception as e:
            print(f"创建本地目录结构失败: {e}")

    def create_local_structure(self, local_base_path, file_tree):
        """递归创建本地目录结构"""
        for child in file_tree['children']:
            if child['is_dir']:
                # 创建子目录
                dir_path = os.path.join(local_base_path, child['name'])
                if not os.path.exists(dir_path):
                    os.makedirs(dir_path)
                self.create_local_structure(dir_path, child)
            else:
                # 处理文件，但不立即下载（只记录信息）
                file_path = os.path.join(local_base_path, child['name'])
                print(f"发现文件: {file_path}")

    def receive_files(self, local_dir_path, server_file_tree):
        """接收并校验文件"""
        try:
            self.receive_files_recursive(local_dir_path, server_file_tree)
        except Exception as e:
            print(f"接收文件失败: {e}")

    def receive_files_recursive(self, local_dir_path, file_tree):
        """递归接收文件"""
        for child in file_tree['children']:
            if not child['is_dir']:
                self.receive_single_file(local_dir_path, child)
            else:
                # 递归处理子目录
                dir_path = os.path.join(local_dir_path, child['name'])
                self.receive_files_recursive(dir_path, child)

    def receive_single_file(self, local_dir_path, file_info):
        """接收单个文件"""
        try:
            file_name = file_info['name']
            file_size = file_info['size']
            expected_hash = file_info['hash']
            # 构建本地文件路径
            local_file_path = os.path.join(local_dir_path, file_name)

            print(f"正在同步文件: {local_file_path}")
            file_header = self.client_socket.recv(4096) # bug:接受file_header，如果缺少这行代码，会导致received_data头部多一个file_header，导致hash计算错误。
            # 发送准备接收消息给服务器
            self.client_socket.send(b"READY_TO_RECEIVE")

            # 接收文件内容
            received_data = b""
            while len(received_data) < file_size:
                data = self.client_socket.recv(min(4096, file_size-len(received_data)))  # 保证接受文件大小与原文件一致，直接用4096有产生bug的可能
                if not data:
                    break
                received_data += data
            # received_data += self.client_socket.recv(4096)
            # 验证文件完整性
            actual_hash = hashlib.md5(received_data).hexdigest()
            print('1.', actual_hash)
            print('2.', expected_hash)
            print('3.', received_data[-100:])
            # actual_hash = expected_hash
            if expected_hash == actual_hash:
                print(f"文件 {file_name} 校验通过")

                # 保存文件到本地
                with open(local_file_path, 'wb') as f:
                    f.write(received_data)
            else:
                print(f"文件 {file_name} 校验失败，重新传输...")
                # self.client_socket.send(b"RETRANSMIT")  # bug：服务器端实际不会确认客户端是否有重传请求
                exit()

        except Exception as e:
            print(f"接收单个文件失败: {e}")


if __name__ == "__main__":
    client = FileSyncClient()

    if client.connect_to_server():
        try:
            # 同步到当前目录
            client.sync_directory("./synced_files")
        finally:
            client.disconnect_from_server()